// Copyright 2011, Vanya Davidenko.
// Используемая кодировка: UTF-8.

#ifndef LIBV_FILE_BASIC_FILE_H_
#define LIBV_FILE_BASIC_FILE_H_

#include <assert.h>
#include <string>
#include <libv/uncopyable.h>

#if defined(_WIN32)
# include <libv/windows_lean.h>
typedef fpos_t fpos64;
typedef unsigned __int64 fsize;
typedef HANDLE FileHandle;
typedef DWORD ErrorCode;

# define LIBV_FILE_NO_ERROR ERROR_SUCCESS
# define LIBV_FILE_ERROR_BAD_ARGUMENTS ERROR_BAD_ARGUMENTS
# define LIBV_FILE_ERROR_INVALID_HANDLE ERROR_INVALID_HANDLE
# define LIBV_FILE_INVALID_HANDLE INVALID_HANDLE_VALUE


#elif defined(__linux__)
# ifndef _LARGEFILE64_SOURCE
#  define _LARGEFILE64_SOURCE
# endif

# ifndef _FILE_OFFSET_BITS
#  define _FILE_OFFSET_BITS 64
# endif

# ifndef _XOPEN_SOURCE
#  define _XOPEN_SOURCE 600
# else
#  if _XOPEN_SOURCE < 600
#   error "_XOPEN_SOURCE was previously defined lesser then 600"
#  endif
# endif


# include <errno.h>
# include <sys/types.h>
# include <unistd.h>

typedef off64_t fpos64;
typedef uint64_t fsize;
typedef int FileHandle;
typedef int ErrorCode;

# define LIBV_FILE_NO_ERROR ErrorCode(0)
# define LIBV_FILE_ERROR_BAD_ARGUMENTS EINVAL
# define LIBV_FILE_ERROR_INVALID_HANDLE EBADF
# define LIBV_FILE_INVALID_HANDLE FileHandle(-1)


#else
# error "This header should be used in Windows or Linux 2.6+."
#endif  // _WIN32



namespace libv
{

class BasicFile : libv::Uncopyable
{
  public:
    struct OpenType
    {
        enum
        {
            kInvalidOpen = 0,  // для отладки
            kCreateIfNotExist = 1,
            kOpenAlways = 2,
            kOpenExisting = 3
        };

        static bool is_valid(const int lock_type)
        {
            return lock_type == kCreateIfNotExist ||
                   lock_type == kOpenAlways ||
                   lock_type == kOpenExisting;
        }
    };


    // Структура с константами используется потому, что enum не проверяет
    // корректность значения (enum E { A = 1 }; enum E e(2); // e некорректно).
    struct LockType
    {
        enum
        {
            kInvalidLock = 0,  // для отладки
            kNoLock = 1,
            kSharedLock = 2,
            kExclusiveLock = 3
        };

        static bool is_valid(const int lock_type)
        {
            return lock_type == kNoLock ||
                   lock_type == kSharedLock ||
                   lock_type == kExclusiveLock;
        }
    };


    struct PreallocType
    {
        // kNoPrealloc больше нет, так как из-за того, что при необходимости
        // записи после конца файла ОС сама производит преаллокацию, таким
        // образом семантика kNoPrealloc и kRecPrealloc совпадали.

        enum
        {
            kInvalidPrealloc = 0,  // для отладки
            kRecPrealloc = 1,
            kConstIncPrealloc = 2,
            kPercIncPrealloc = 3
        };

        static bool is_valid(const int prealloc_type)
        {
            return prealloc_type == kRecPrealloc ||
                   prealloc_type == kConstIncPrealloc ||
                   prealloc_type == kPercIncPrealloc;
        }
    };


    struct Prealloc
    {
        // Проверку is_valid делает метод set_prea
        Prealloc(const int type_, const fsize param_)
            : type(type_), param(param_) {}

        int type;
        fsize param;
    };



    inline BasicFile();

    inline bool is_opened() const;

    inline bool Close() const;
    inline bool Close();


    // -1 в случае ошибки
    inline fpos64 Size() const;
    inline bool Resize(const fpos64 size);

    inline bool Flush();

    // Не атомарно: сначала снимается текущая блокировка!
    inline bool TryLock(const int lock_type) const;
    inline bool Lock(const int lock_type) const;
    inline bool Unlock() const;


    inline void set_auto_remove(const bool on);
    inline bool set_prealloc(const Prealloc& prealloc);


    inline FileHandle file_handle() const;
    inline ErrorCode last_error() const;
    inline int lock_type() const;

    inline ::std::string file_path() const;
    inline bool read_only() const;
    inline bool auto_remove() const;
    inline Prealloc prealloc() const;

  protected:
    inline ~BasicFile();

    // Использование защищенных методов-реализаций позволяет
    // избежать виртуальных методов.
    inline bool BasicOpen(const ::std::string& file_path,
                          const int open_type,
                          const bool enable_read_ahead_caching,
                          const int additional_flags,
                          const bool read_only) const;

    inline bool PreallocateAtLeast(const fpos64 max_offset);

    mutable FileHandle file_handle_;
    mutable ErrorCode last_error_;
    mutable int lock_type_;


  private:
    inline bool BasicLock(const int lock_type, const bool block) const;
    inline void Reset() const;

    inline FileHandle BasicOpenImpl(const ::std::string& file_path,
                                    const int open_type,
                                    const bool enable_read_ahead_caching,
                                    const int additional_flags,
                                    const bool read_only) const;
    inline bool CloseImpl() const;
    inline fpos64 SizeImpl() const;
    inline bool ResizeImpl(const fpos64 size);
    inline bool FlushImpl();
    inline bool BasicLockImpl(const int lock_type, const bool block) const;
    inline bool UnlockImpl() const;



    mutable fpos64 file_size_;
    mutable ::std::string file_path_;
    mutable bool read_only_;

    mutable bool auto_remove_;
    Prealloc prealloc_;
};

}  // libv



#include <libv/file/basic_file.hpp>
#if defined(_WIN32)
# include <libv/file/windows/basic_file_impl.hpp>
#elif defined(__linux__)
# include <libv/file/linux/basic_file_impl.hpp>
#else
# error "This header should be used in Windows or Linux 2.6+."
#endif

#endif  // LIBV_FILE_BASIC_FILE_H_
